﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Framework.Config;
using System.Reflection;
using Framework.Event;
using Framework.Infrastructure;
using Framework.Command;
using Framework.Domain;

namespace Framework.Bus
{
    public static class MessageDispatcherUtility
    {
        public static void RegisterHandlers(IMessageDispatcher dispatcher, HandlerKind kind, bool isAsyncHandler)
        {
            dispatcher.ClearRegistration();
            var handlerElements = ConfigurationReader.Instance
                                                     .GetConfigurationSection<FrameworkConfigurationSection>()
                                                     .Handlers;
            if (handlerElements != null)
            {
                foreach (HandlerElement handlerElement in handlerElements)
                {
                    if (handlerElement.Kind != kind)
                    {
                        continue;
                    }
                    try
                    {
                        switch (handlerElement.SourceType)
                        {
                            case HandlerSourceType.Type:
                                Type type = Type.GetType(handlerElement.Source);
                                RegisterHandlerFromType(dispatcher, handlerElement.Kind, type, isAsyncHandler);
                                break;
                            case HandlerSourceType.Assembly:
                                Assembly assembly = Assembly.Load(handlerElement.Source);
                                RegisterHandlerFromAssembly(dispatcher, handlerElement.Kind, assembly, isAsyncHandler);
                                break;
                        }
                    }
                    catch
                    {
                        continue;
                    }
                }
            }
        }

        #region Private Methods
        private static void RegisterHandlerFromAssembly(IMessageDispatcher dispatcher, HandlerKind kind, Assembly assembly, bool isAsyncHandler)
        {
            var exportedTypes = assembly.GetExportedTypes();
            foreach (var type in exportedTypes)
            {
                RegisterHandlerFromType(dispatcher, kind, type, isAsyncHandler);
            }
        }

        private static void RegisterHandlerFromType(IMessageDispatcher dispatcher, HandlerKind kind, Type type, bool isAsyncHandler)
        {
            Type dispatcherType = dispatcher.GetType();
            switch (kind)
            {
                case HandlerKind.Event:
                    if (!type.IsAbstract &&
                        typeof(IEventSubscriber).IsAssignableFrom(type))
                    {
                        
                        //var genericInterfaceTypes = type.GetInterfaces().Where(t => t.IsGenericType && t.GetGenericTypeDefinition().Equals(genericTypeDefinition));
                        foreach (var method in type.GetMethods().ToList())
                        {
                            var eventHandlerAttr = method.GetCustomAttribute<EventHandlerAttribute>();
                            if (eventHandlerAttr != null && eventHandlerAttr.IsAsync == isAsyncHandler)
                            {
                                var eventParam = method.GetParameters().FirstOrDefault();
                                if (eventParam != null)
                                {
                                    var eventType = eventParam.ParameterType;
                                    if (typeof(IDomainEvent).IsAssignableFrom(eventType))
                                    {
                                        dispatcher.Register(eventType, type, method);
                                    }
                                }
                            }
                        }
                    }
                    break;
                case HandlerKind.Command:
                    if (!type.IsAbstract &&
                       typeof(ICommandHandler).IsAssignableFrom(type))
                    {
                        foreach (var method in type.GetMethods().ToList())
                        {
                            var commandHandlerAttr = method.GetCustomAttribute<CommandHandlerAttribute>();
                            if (commandHandlerAttr != null)
                            {
                                var commandParam = method.GetParameters().FirstOrDefault();
                                if (commandParam != null)
                                {
                                    var commandType = commandParam.ParameterType;
                                    if (typeof(ICommand).IsAssignableFrom(commandType))
                                    {
                                        dispatcher.Register(commandType, type, method);
                                    }
                                }
                            }
                        }
                    }
                    break;
                case HandlerKind.AggregateRoot:
                    if (!type.IsAbstract &&
                        typeof(AggregateRoot).IsAssignableFrom(type))
                    {

                        //var genericInterfaceTypes = type.GetInterfaces().Where(t => t.IsGenericType && t.GetGenericTypeDefinition().Equals(genericTypeDefinition));
                        foreach (var method in type.GetMethods(BindingFlags.Public 
                                                             | BindingFlags.NonPublic 
                                                             | BindingFlags.Instance))
                        {
                            var eventHandlerAttr = method.GetCustomAttribute<EventHandlerAttribute>();
                            if (eventHandlerAttr != null)
                            {
                                var eventParam = method.GetParameters().FirstOrDefault();
                                if (eventParam != null)
                                {
                                    var eventType = eventParam.ParameterType;
                                    if (typeof(IDomainEvent).IsAssignableFrom(eventType))
                                    {
                                        dispatcher.Register(eventType, type, method);
                                    }
                                }
                            }
                        }
                    }
                    break;
                default: return;
            }
        }
        #endregion
    }
}
